﻿@page "/emails"
@using System.Net
@using System.Text
@using System.Text.Json
@using AliasVault.Client.Main.Pages.Emails.Models
@using AliasVault.Shared.Models.Spamok
@using AliasVault.Shared.Models.WebApi
@using AliasVault.Shared.Models.WebApi.Email
@using AliasVault.Client.Main.Components
@using AliasVault.Client.Main.Components.Email
@using AliasVault.Client.Main.Services
@inherits MainBase
@inject HttpClient HttpClient
@inject ILogger<Home> Logger
@inject MinDurationLoadingService LoadingService
@using Microsoft.Extensions.Localization
@implements IAsyncDisposable

<LayoutPageTitle>@Localizer["PageTitle"]</LayoutPageTitle>

@if (EmailModalVisible)
{
    <EmailModal Email="EmailModalEmail" IsSpamOk="false" OnClose="CloseEmailModal" OnEmailDeleted="RefreshData" />
}

<PageHeader
    BreadcrumbItems="@BreadcrumbItems"
    Title="@Localizer["PageTitle"]"
    Description="@Localizer["PageDescription"]">
    <CustomActions>
        @if (DbService.Settings.AutoEmailRefresh)
        {
            <div class="w-3 h-3 mr-2 rounded-full bg-primary-300 border-2 border-primary-100 animate-pulse" title="@Localizer["AutoRefreshEnabledTooltip"]"></div>
        }
        <RefreshButton OnClick="RefreshData" ButtonText="@Localizer["RefreshButton"]" />
    </CustomActions>
</PageHeader>

@if (IsLoading)
{
    <div class="px-4">
        <!-- Mobile Skeleton -->
        <div class="block lg:hidden mt-6">
            <div class="bg-white border rounded-lg dark:bg-gray-800 dark:border-gray-700 overflow-hidden">
                <ul class="divide-y divide-gray-200 dark:divide-gray-600">
                    @for (int i = 0; i < 5; i++)
                    {
                        <EmailRowSkeleton />
                    }
                </ul>
            </div>
        </div>

        <!-- Desktop Skeleton -->
        <div class="hidden lg:flex mt-6 h-[calc(100vh-300px)] min-h-[600px]">
            <!-- Left Sidebar Skeleton -->
            <div class="w-1/4 bg-white border rounded-l-lg dark:bg-gray-800 dark:border-gray-700 overflow-hidden">
                <div class="h-full overflow-y-auto">
                    <ul class="divide-y divide-gray-200 dark:divide-gray-600">
                        @for (int i = 0; i < 5; i++)
                        {
                            <EmailRowSkeleton />
                        }
                    </ul>
                </div>
            </div>

            <!-- Right Panel Skeleton -->
            <div class="w-3/4">
                <EmailPreviewSkeleton />
            </div>
        </div>
    </div>
}
else if (NoEmailClaims)
{
    <div class="p-4 mx-4 mt-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 dark:bg-gray-800">
        <div class="px-4 py-2 text-gray-400 rounded">
            <p class="text-gray-500 dark:text-gray-400">@Localizer["NoEmailClaimsMessage"]</p>
        </div>
    </div>
}
else
{
    <div class="px-4">
        <!-- Mobile Layout (sm and down) - Modal behavior with traditional pagination -->
        <div class="block lg:hidden mt-6">
            <ResponsivePaginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged"/>

            <div class="bg-white border rounded-lg dark:bg-gray-800 dark:border-gray-700 overflow-hidden mt-4">
                <ul class="divide-y divide-gray-200 dark:divide-gray-600">
                    @if (EmailList.Count == 0)
                    {
                        <li class="p-4 text-center text-gray-500 dark:text-gray-300">
                            @Localizer["NoEmailsReceivedMessage"]
                        </li>
                    }
                    else
                    {
                        @foreach (var email in EmailList)
                        {
                            <EmailRow
                                Email="email"
                                OnEmailClick="ShowAliasVaultEmailInModal"
                                IsSelected="false"
                                IsNewEmail="@(NewEmailIds.Contains(email.Id))" />
                        }
                    }
                </ul>
            </div>
        </div>

        <!-- Desktop Layout (lg and up) - Sidebar and Preview with Load More -->
        <div class="hidden lg:flex mt-6 rounded-lg overflow-hidden">
            @if (EmailList.Count == 0)
            {
                <!-- Single row message for desktop when no emails -->
                <div class="w-full bg-white border rounded-lg dark:bg-gray-800 dark:border-gray-700 overflow-hidden">
                    <div class="p-4 text-center text-gray-500 dark:text-gray-300">
                        @Localizer["NoEmailsReceivedMessage"]
                    </div>
                </div>
            }
            else
            {
                <div class="w-full h-[calc(100vh-300px)] min-h-[600px] flex rounded-lg overflow-hidden">
                    <!-- Left Sidebar - Email List -->
                    <div class="w-1/4 bg-white border border-r-0 dark:bg-gray-800 dark:border-gray-700 flex flex-col">
                        <div class="flex-1 overflow-y-auto" id="email-list-container">
                            <ul>
                                @foreach (var email in EmailList)
                                {
                                    <EmailRow
                                        Email="email"
                                        OnEmailClick="SelectEmailForPreview"
                                        IsSelected="@(SelectedEmailId == email.Id)"
                                        IsNewEmail="@(NewEmailIds.Contains(email.Id))" />
                                }
                                <!-- Load More Button for Desktop -->
                                @if (HasMoreEmails && EmailList.Count > 0)
                                {
                                    <li class="border-t border-gray-200 dark:border-gray-600 p-3 bg-gray-50 dark:bg-gray-700">
                                        <button @onclick="LoadMoreEmails"
                                                disabled="@IsLoadingMore"
                                                class="w-full px-4 py-2 text-sm font-medium text-primary-600 bg-primary-50 border border-primary-200 rounded-md hover:bg-primary-100 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed dark:text-primary-400 dark:bg-primary-900/20 dark:border-primary-800 dark:hover:bg-primary-900/30">
                                            @if (IsLoadingMore)
                                            {
                                                <span class="flex items-center justify-center">
                                                    <svg class="animate-spin -ml-1 mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24">
                                                        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                                                        <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                                                    </svg>
                                                    @Localizer["LoadingText"]
                                                </span>
                                            }
                                            else
                                            {
                                                <span>@string.Format(Localizer["LoadMoreButtonText"], TotalRecords - EmailList.Count)</span>
                                            }
                                        </button>
                                    </li>
                                }
                            </ul>
                        </div>
                    </div>

                    <!-- Right Panel - Email Preview -->
                    <div class="w-3/4">
                        <EmailPreview
                            Email="SelectedEmail"
                            IsSpamOk="false"
                            OnEmailDeleted="HandleEmailDeleted"
                            CredentialId="@GetSelectedEmailCredentialId()"
                            CredentialName="@GetSelectedEmailCredentialName()"
                            OnCredentialClick="NavigateToCredential" />
                    </div>
                </div>
            }
        </div>
    </div>
}

@code {
    private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Emails.Home", "AliasVault.Client");
    private List<MailListViewModel> EmailList { get; set; } = [];
    private bool IsLoading => LoadingService.IsLoading("emails");
    private int CurrentPage { get; set; } = 1;
    private int PageSize { get; set; } = 50;
    private int TotalRecords { get; set; }
    private bool EmailModalVisible { get; set; }
    private bool NoEmailClaims { get; set; }
    private EmailApiModel EmailModalEmail { get; set; } = new();
    private int? SelectedEmailId { get; set; }
    private EmailApiModel? SelectedEmail { get; set; }
    private bool IsLoadingMore { get; set; }
    private bool HasMoreEmails => TotalRecords > EmailList.Count;

    // Auto-refresh related properties
    private const int ACTIVE_TAB_REFRESH_INTERVAL = 2000; // 2 seconds
    private CancellationTokenSource? _pollingCts;
    private DotNetObjectReference<Home>? _dotNetRef;
    private bool _isPageVisible = true;
    private HashSet<int> NewEmailIds { get; set; } = new();
    private HashSet<int> _knownEmailIds = new();

    /// <summary>
    /// Callback invoked by JavaScript when the page visibility changes. This is used to start/stop the polling for new emails.
    /// </summary>
    /// <param name="isVisible">Indicates whether the page is visible or not.</param>
    [JSInvokable]
    public void OnVisibilityChange(bool isVisible)
    {
        _isPageVisible = isVisible;

        if (isVisible && DbService.Settings.AutoEmailRefresh)
        {
            // Start polling if visible and auto-refresh is enabled
            StartPolling();
        }
        else
        {
            // Stop polling if hidden
            StopPolling();
        }

        // If becoming visible, do an immediate refresh
        if (isVisible)
        {
            _ = CheckForNewEmails();
        }
    }

    private void StartPolling()
    {
        // If already polling, no need to start again
        if (_pollingCts != null) {
            return;
        }

        _pollingCts = new CancellationTokenSource();

        // Start polling task
        _ = PollForNewEmails(_pollingCts.Token);
    }

    private void StopPolling()
    {
        if (_pollingCts != null)
        {
            _pollingCts.Cancel();
            _pollingCts.Dispose();
            _pollingCts = null;
        }
    }

    private async Task PollForNewEmails(CancellationToken cancellationToken)
    {
        try
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                await CheckForNewEmails();
                await Task.Delay(ACTIVE_TAB_REFRESH_INTERVAL, cancellationToken);
            }
        }
        catch (OperationCanceledException)
        {
            // Normal cancellation, ignore.
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error in email refresh polling");
        }
    }

    /// <summary>
    /// Load emails from the server with the given parameters.
    /// </summary>
    private async Task<(List<MailListViewModel> Emails, int TotalRecords, int CurrentPage, int PageSize)?> LoadEmailsFromServerAsync(int page, int pageSize, List<string> emailClaimList)
    {
        if (emailClaimList.Count == 0)
        {
            return null;
        }

        var requestModel = new MailboxBulkRequest
        {
            Page = page,
            PageSize = pageSize,
            Addresses = emailClaimList,
        };

        var request = new HttpRequestMessage(HttpMethod.Post, $"v1/EmailBox/bulk");
        request.Content = new StringContent(JsonSerializer.Serialize(requestModel), Encoding.UTF8, "application/json");

        try
        {
            var response = await HttpClient.SendAsync(request);
            if (response.IsSuccessStatusCode)
            {
                var mailbox = await response.Content.ReadFromJsonAsync<MailboxBulkResponse>();
                if (mailbox?.Mails != null)
                {
                    var context = await DbService.GetDbContextAsync();
                    var credentialLookup = await context.Credentials
                        .Include(x => x.Service)
                        .Include(x => x.Alias)
                        .Where(x => x.Alias.Email != null)
                        .GroupBy(x => x.Alias.Email!.ToLower())
                        .ToDictionaryAsync(
                            g => g.Key,
                            g => new { Id = g.First().Id, ServiceName = g.First().Service.Name ?? "Unknown" }
                        );

                    List<MailboxEmailApiModel> decryptedEmailList;
                    try
                    {
                        decryptedEmailList = await EmailService.DecryptEmailList(mailbox.Mails);
                    }
                    catch (InvalidOperationException ex) when (ex.Message.Contains("Sequence contains no matching element"))
                    {
                        // Handle case where encryption keys are not available for some emails
                        Logger.LogWarning(ex, "Failed to decrypt some emails due to missing encryption keys");
                        return null;
                    }

                    var emails = decryptedEmailList.Select(email =>
                    {
                        var toEmail = email.ToLocal + "@" + email.ToDomain;
                        var credentialInfo = credentialLookup.TryGetValue(toEmail.ToLower(), out var info)
                            ? info
                            : new { Id = Guid.Empty, ServiceName = "Unknown" };

                        return new MailListViewModel
                        {
                            Id = email.Id,
                            Date = email.DateSystem,
                            FromName = email.FromDisplay,
                            FromEmail = email.FromLocal + "@" + email.FromDomain,
                            ToEmail = toEmail,
                            Subject = email.Subject,
                            MessagePreview = email.MessagePreview,
                            CredentialId = credentialInfo.Id,
                            CredentialName = credentialInfo.ServiceName,
                            HasAttachments = email.HasAttachments,
                        };
                    }).ToList();

                    return (emails, mailbox.TotalRecords, mailbox.CurrentPage, mailbox.PageSize);
                }
            }
            else
            {
                var errorContent = await response.Content.ReadAsStringAsync();
                var errorResponse = JsonSerializer.Deserialize<ApiErrorResponse>(errorContent);
                switch (response.StatusCode)
                {
                    case HttpStatusCode.BadRequest:
                        if (errorResponse != null)
                        {
                            switch (errorResponse.Code)
                            {
                                case "CLAIM_DOES_NOT_EXIST":
                                    GlobalNotificationService.AddErrorMessage(Localizer["ClaimDoesNotExistError"], true);
                                    break;
                                default:
                                    throw new ArgumentException(errorResponse.Message);
                            }
                        }
                        break;
                    case HttpStatusCode.Unauthorized:
                        throw new UnauthorizedAccessException(errorResponse?.Message);
                    default:
                        throw new WebException(errorResponse?.Message);
                }
            }
        }
        catch (Exception ex)
        {
            GlobalNotificationService.AddErrorMessage(ex.Message, true);
            Logger.LogError(ex, "An error occurred while loading emails from server");
        }

        return null;
    }

    /// <summary>
    /// Check for new emails without disrupting the current view.
    /// </summary>
    private async Task CheckForNewEmails()
    {
        if (!_isPageVisible || !DbService.Settings.AutoEmailRefresh)
        {
            return;
        }

        try
        {
            var emailClaimList = await DbService.GetEmailClaimListAsync();
            var result = await LoadEmailsFromServerAsync(1, 5, emailClaimList);

            if (result.HasValue)
            {
                var (newEmails, _, _, _) = result.Value;

                // Check for new emails
                var newEmailIds = newEmails.Where(email => !_knownEmailIds.Contains(email.Id)).Select(email => email.Id).ToList();

                // Update the known email IDs
                foreach (var email in newEmails)
                {
                    _knownEmailIds.Add(email.Id);
                }

                // Add new emails to the list and mark them as new
                if (newEmailIds.Count > 0)
                {
                    // Add new emails to the beginning of the list
                    var emailsToAdd = newEmails.Where(e => newEmailIds.Contains(e.Id)).ToList();
                    EmailList.InsertRange(0, emailsToAdd);

                    // Update total records
                    TotalRecords += emailsToAdd.Count;

                    // Mark emails as new
                    NewEmailIds.UnionWith(newEmailIds);

                    // Remove new email indicators after 30 seconds
                    _ = Task.Delay(30000).ContinueWith(_ =>
                    {
                        NewEmailIds.ExceptWith(newEmailIds);
                        InvokeAsync(StateHasChanged);
                    });

                    StateHasChanged();
                }
            }
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "An error occurred while checking for new emails");
        }
    }

    /// <inheritdoc />
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();

        // Create a single object reference for JS interop
        _dotNetRef = DotNetObjectReference.Create(this);
        await JsInteropService.RegisterVisibilityCallback(_dotNetRef);

        // Only start polling if auto-refresh is enabled and page is visible
        if (DbService.Settings.AutoEmailRefresh && _isPageVisible)
        {
            StartPolling();
        }
    }

    /// <inheritdoc />
    public async ValueTask DisposeAsync()
    {
        // Stop polling
        StopPolling();

        // Unregister the visibility callback using the same reference
        if (_dotNetRef != null)
        {
            await JsInteropService.UnregisterVisibilityCallback(_dotNetRef);
            _dotNetRef.Dispose();
        }
    }

    /// <inheritdoc />
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (firstRender)
        {
            await RefreshData();
        }
    }

    private void HandlePageChanged(int newPage)
    {
        CurrentPage = newPage;
        _ = RefreshData();
    }

    private async Task RefreshData()
    {
        LoadingService.StartLoading("emails", 300, StateHasChanged);
        NoEmailClaims = false;
        CloseEmailModal();

        // Clear selected email when refreshing
        SelectedEmailId = null;
        SelectedEmail = null;

        // Reset pagination for fresh load
        CurrentPage = 1;
        EmailList.Clear();
        NewEmailIds.Clear();
        _knownEmailIds.Clear();

        var emailClaimList = await DbService.GetEmailClaimListAsync();

        if (emailClaimList.Count == 0)
        {
            LoadingService.FinishLoading("emails", StateHasChanged);
            NoEmailClaims = true;
            return;
        }

        var result = await LoadEmailsFromServerAsync(CurrentPage, PageSize, emailClaimList);

        if (result.HasValue)
        {
            var (emails, totalRecords, currentPage, pageSize) = result.Value;
            await UpdateMailboxEmails(emails, totalRecords, currentPage, pageSize, false);
        }

        LoadingService.FinishLoading("emails", StateHasChanged);
    }

    /// <summary>
    /// Update the local mailbox emails.
    /// </summary>
    private async Task UpdateMailboxEmails(List<MailListViewModel> emails, int totalRecords, int currentPage, int pageSize, bool appendToList = false)
    {
        if (appendToList)
        {
            EmailList.AddRange(emails);
        }
        else
        {
            EmailList = emails;
            // Initialize known email IDs for auto-refresh - don't mark existing emails as new
            _knownEmailIds = new HashSet<int>(emails.Select(e => e.Id));
            // Clear any existing new email indicators since this is the initial load
            NewEmailIds.Clear();
        }

        CurrentPage = currentPage;
        PageSize = pageSize;
        TotalRecords = totalRecords;

        // Auto-select first email on desktop layout if none is selected and emails exist (only for initial load)
        if (!appendToList && EmailList.Count > 0 && SelectedEmailId == null)
        {
            var firstEmail = EmailList[0];
            SelectedEmailId = firstEmail.Id;
            await LoadSelectedEmailForPreview(firstEmail.Id);
        }
    }

    /// <summary>
    /// Load recent emails from AliasVault.
    /// </summary>
    private async Task ShowAliasVaultEmailInModal(int emailId)
    {
        // Remove new email mark when email is clicked
        NewEmailIds.Remove(emailId);

        EmailApiModel? mail = await HttpClient.GetFromJsonAsync<EmailApiModel>($"v1/Email/{emailId}");
        if (mail != null)
        {
            // Decrypt the email content locally.
            var context = await DbService.GetDbContextAsync();
            var privateKey = await context.EncryptionKeys.FirstOrDefaultAsync(x => x.PublicKey == mail.EncryptionKey);
            if (privateKey is not null)
            {
                mail = await EmailService.DecryptEmail(mail);
            }

            EmailModalEmail = mail;
            EmailModalVisible = true;
            StateHasChanged();
        }
    }

    /// <summary>
    /// Close the email modal.
    /// </summary>
    private void CloseEmailModal()
    {
        EmailModalVisible = false;
        StateHasChanged();
    }

    /// <summary>
    /// Navigate to the credential page.
    /// </summary>
    private void NavigateToCredential(Guid credentialId)
    {
        NavigationManager.NavigateTo($"/credentials/{credentialId}");
    }

    /// <summary>
    /// Select an email for preview (desktop layout).
    /// </summary>
    private async Task SelectEmailForPreview(int emailId)
    {
        // Remove new email mark when email is clicked
        NewEmailIds.Remove(emailId);

        SelectedEmailId = emailId;
        await LoadSelectedEmailForPreview(emailId);
    }

    /// <summary>
    /// Load the full email data for preview.
    /// </summary>
    private async Task LoadSelectedEmailForPreview(int emailId)
    {
        try
        {
            EmailApiModel? mail = await HttpClient.GetFromJsonAsync<EmailApiModel>($"v1/Email/{emailId}");
            if (mail != null)
            {
                // Decrypt the email content locally.
                var context = await DbService.GetDbContextAsync();
                var privateKey = await context.EncryptionKeys.FirstOrDefaultAsync(x => x.PublicKey == mail.EncryptionKey);
                if (privateKey is not null)
                {
                    mail = await EmailService.DecryptEmail(mail);
                }

                SelectedEmail = mail;
                StateHasChanged();
            }
        }
        catch (Exception ex)
        {
            GlobalNotificationService.AddErrorMessage(string.Format(Localizer["LoadEmailsFailedMessage"], ex.Message), true);
            Logger.LogError(ex, "An error occurred while loading email for preview");
        }
    }

    /// <summary>
    /// Handle email deletion from preview panel.
    /// </summary>
    private async Task HandleEmailDeleted(int emailId)
    {
        // Remove the deleted email from the list
        var deletedEmailIndex = EmailList.FindIndex(e => e.Id == emailId);
        EmailList.RemoveAll(e => e.Id == emailId);

        // Remove from tracking sets
        NewEmailIds.Remove(emailId);
        _knownEmailIds.Remove(emailId);

        // Update total records
        TotalRecords = Math.Max(0, TotalRecords - 1);

        // Handle selection logic
        if (SelectedEmailId == emailId)
        {
            SelectedEmailId = null;
            SelectedEmail = null;

            // Try to select the next email in the list
            if (EmailList.Count > 0)
            {
                // If we deleted the first email, select the new first email
                if (deletedEmailIndex == 0)
                {
                    var nextEmail = EmailList[0];
                    SelectedEmailId = nextEmail.Id;
                    await LoadSelectedEmailForPreview(nextEmail.Id);
                }
                // If we deleted an email in the middle, select the email at the same index (or the last one if index is out of bounds)
                else if (deletedEmailIndex > 0)
                {
                    var newIndex = Math.Min(deletedEmailIndex, EmailList.Count - 1);
                    var nextEmail = EmailList[newIndex];
                    SelectedEmailId = nextEmail.Id;
                    await LoadSelectedEmailForPreview(nextEmail.Id);
                }
            }
        }

        StateHasChanged();
    }

    /// <summary>
    /// Get the credential ID for the currently selected email.
    /// </summary>
    private Guid GetSelectedEmailCredentialId()
    {
        if (SelectedEmailId == null) return Guid.Empty;

        var selectedEmailListItem = EmailList.FirstOrDefault(e => e.Id == SelectedEmailId);
        return selectedEmailListItem?.CredentialId ?? Guid.Empty;
    }

    /// <summary>
    /// Get the credential name for the currently selected email.
    /// </summary>
    private string GetSelectedEmailCredentialName()
    {
        if (SelectedEmailId == null) return string.Empty;

        var selectedEmailListItem = EmailList.FirstOrDefault(e => e.Id == SelectedEmailId);
        return selectedEmailListItem?.CredentialName ?? string.Empty;
    }

    /// <summary>
    /// Load more emails for the desktop view.
    /// </summary>
    private async Task LoadMoreEmails()
    {
        if (IsLoadingMore || !HasMoreEmails) return;

        IsLoadingMore = true;
        StateHasChanged();

        try
        {
            var emailClaimList = await DbService.GetEmailClaimListAsync();
            var result = await LoadEmailsFromServerAsync(CurrentPage + 1, PageSize, emailClaimList);

            if (result.HasValue)
            {
                var (emails, totalRecords, currentPage, pageSize) = result.Value;
                await UpdateMailboxEmails(emails, totalRecords, currentPage, pageSize, true); // Append to existing list
            }
        }
        catch (Exception ex)
        {
            GlobalNotificationService.AddErrorMessage(string.Format(Localizer["LoadMoreEmailsFailedMessage"], ex.Message), true);
            Logger.LogError(ex, "An error occurred while loading more emails");
        }
        finally
        {
            IsLoadingMore = false;
            StateHasChanged();
        }
    }
}
